﻿#include "FITKGraphObjectPredefinedFieldBase.h"

// VTK
#include <vtkPoints.h>
#include <vtkUnstructuredGrid.h>
#include <vtkPointData.h>
#include <vtkProperty.h>
#include <vtkGlyph3D.h>
#include <vtkDistanceToCamera.h>
#include <vtkRegularPolygonSource.h>
#include <vtkPointSource.h>
#include <vtkRenderer.h>
#include <vtkCamera.h>
#include <vtkIntArray.h>
#include <vtkSelectionNode.h>
#include <vtkSelection.h>
#include <vtkExtractSelection.h>
#include <vtkIdTypeArray.h>
#include <vtkIdList.h>

// Graph
#include "FITK_Interface/FITKVTKAlgorithm/FITKGraphActor.h"
#include "FITKGraphCommons.h"
#include "FITK_Interface/FITKVTKAlgorithm/FITKPolygonSource.h"

// Render
#include "FITK_Component/FITKRenderWindowVTK/FITKGraphRender.h"

// Global data
#include "FITK_Kernel/FITKCore/FITKDataRepo.h"

// Data
#include "FITK_Interface/FITKInterfaceStructural/FITKAbaAbstractPredefinedField.h"
#include "FITK_Interface/FITKInterfaceModel/FITKAbstractAssInstance.h"
#include "FITK_Interface/FITKInterfaceModel/FITKModelSet.h"
#include "FITK_Interface/FITKInterfaceModel/FITKMeshSurface.h"

// Data manager
#include "FITKSetSurfPolyProvider.h"

namespace Exchange
{
    FITKGraphObjectPredefinedFieldBase::FITKGraphObjectPredefinedFieldBase(AbaqusData::FITKDataCase* caseData, Interface::FITKAbaAbstractPredefinedField* fieldData)
        : FITKGraphObjectMarkBase(caseData, fieldData)
    {
        if (!fieldData)
        {
            return;
        }

        // Set the layer need to be rendered.
        setRenderLayer(0, 1);

        // Save the graph information.
        m_graphInfo.Type = OtherGraph;

        // Save if the bounds are fixed or dynamic.
        m_hasFixedBounds = false;
    }

    FITKGraphObjectPredefinedFieldBase::~FITKGraphObjectPredefinedFieldBase()
    {
        // Delete the grid data.
        deleteVtkObj(m_modelIdArrayLoc); 
        deleteVtkObj(m_pointsLoc);
        deleteVtkObj(m_gridLoc);

        deleteVtkObj(m_modelIdArray);
        deleteVtkObj(m_points);
        deleteVtkObj(m_ugrid);

        // Delete filters.
        deleteVtkObjs(m_glyphList);
        deleteVtkObjs(m_distToCameraList);

        // Delete extractor pointers.
        deleteVtkObj(m_selectNode);
        deleteVtkObj(m_section);
        deleteVtkObj(m_extractSelection);
        deleteVtkObj(m_idArray);

        // Clear sub set ids.
        m_subSetIds.clear();
    }

    void FITKGraphObjectPredefinedFieldBase::setVisible(bool visibility)
    {
        if (m_fActor)
        {
            m_fActor->SetVisibility(visibility);
        }
    }

    void FITKGraphObjectPredefinedFieldBase::init()
    {
        // Initialize the location grid.
        m_gridLoc = vtkUnstructuredGrid::New();
        m_pointsLoc = vtkPoints::New();
        m_gridLoc->SetPoints(m_pointsLoc);

        m_modelIdArrayLoc = vtkIntArray::New();
        m_modelIdArrayLoc->SetNumberOfComponents(1);   

        // Initialize the glyph grid.
        m_ugrid = vtkUnstructuredGrid::New();
        m_points = vtkPoints::New();
        m_ugrid->SetPoints(m_points);

        m_modelIdArray = vtkIntArray::New();
        m_modelIdArray->SetNumberOfComponents(1);

        // Initialize the extractor.
        //@{
        m_selectNode = vtkSelectionNode::New();
        m_section = vtkSelection::New();
        m_extractSelection = vtkExtractSelection::New();
        m_selectNode->SetContentType(vtkSelectionNode::INDICES);
        m_selectNode->SetFieldType(vtkSelectionNode::SelectionField::CELL);
        m_section->AddNode(m_selectNode);
        m_extractSelection->SetInputData(1, m_section);

        m_idArray = vtkIdTypeArray::New();
        m_selectNode->SetSelectionList(m_idArray);
        m_extractSelection->SetInputData(m_ugrid);
        //@}
    }

    void FITKGraphObjectPredefinedFieldBase::initActors()
    {
        // Create the actor for lines.
        m_fActor = FITKGraphActor::New();
        m_fActor->setGraphObject(this);
        m_fActor->SetPickable(false);
        m_fActor->setScalarVisibility(false);

        // Add actors.
        addActor(m_fActor);
    }

    void FITKGraphObjectPredefinedFieldBase::clearModelsInfo()
    {
        // Clear model array.
        resetVtkObj(m_modelIdArray);
        resetVtkObj(m_modelIdArrayLoc);
        
        m_modelIds.clear();
        m_modelVisibilityHash.clear();
        m_subSetIds.clear();

        // Reset the component ids.
        m_locationCompId = -1;
    }

    void FITKGraphObjectPredefinedFieldBase::clearData()
    {
        // Clear data.
        resetVtkObj(m_pointsLoc);
        resetVtkObj(m_gridLoc);
        resetVtkObj(m_points);
        resetVtkObj(m_ugrid);

        // Clear the model infomation.
        clearModelsInfo();
    }

    bool FITKGraphObjectPredefinedFieldBase::dataChanged()
    {
        // Override if neccessary.
        return false;
    }

    void FITKGraphObjectPredefinedFieldBase::highlight(QColor color)
    {
        // If the object is invisible, then return.
        if (!getDataVisibility())
        {
            return;
        }

        // Get the interactions or constraint highlight color.
        double color3[3] = { 0 ,0, 0 };
        if (color.isValid())
        {
            FITKGraphCommons::QColorToDouble3(color, color3);
        }
        else
        {
            FITKGraphCommons::QColorToDouble3(FITKGraphCommons::s_constraintHighlightColor, color3);
        }      
        
        // Set the highlight color for glyph.
        if (m_fActor)
        {
            m_fActor->GetProperty()->SetColor(color3);
        }

        FITKGraphObject3D::highlight();
    }

    void FITKGraphObjectPredefinedFieldBase::disHighlight()
    {
        // Get the interactions or constraint default color.
        double color3[3] = { 0 ,0, 0 };
        FITKGraphCommons::QColorToDouble3(FITKGraphCommons::s_constraintColor, color3);

        // Set the default color for glyph.
        if (m_fActor)
        {
            m_fActor->GetProperty()->SetColor(color3);
        }     

        FITKGraphObject3D::disHighlight();
    }

    void FITKGraphObjectPredefinedFieldBase::update(bool forceUpdate)
    {
        // Update grid data after the field's set or surface changed.
        Interface::FITKAbaAbstractPredefinedField* fieldData = dynamic_cast<Interface::FITKAbaAbstractPredefinedField*>(_dataObj);
        if (!m_ugrid || !m_gridLoc || !fieldData)
        {
            return;
        }

        // Get the component id.
        int compId = fieldData->getComponentID();

        // Check if the component exists.
        bool isCompValid = getComponentValid(compId);

        bool needUpdate = false;
        if (!isCompValid)
        {
            needUpdate = true;

            // Clear data.
            clearData();
        }

        // Check if the locations and the values changed.
        needUpdate |= (compId != m_locationCompId || forceUpdate);
        needUpdate |= dataChanged();
      
        if (!needUpdate)
        {
            // Update the visibility only.
            updateVisibility();
            return;
        }

        // Clear data first.
        clearData();

        bool flag = generatePolyData(fieldData);
        if (!flag)
        {
            return;
        }

        // Update the visibility at last.
        updateVisibility();

        // Modify actors.
        updateActors();
    }

    void FITKGraphObjectPredefinedFieldBase::updateVisibility()
    {
        // Update the full actor.
        FITKGraphObject3D::updateVisibility();

        if (!m_idArray || !m_modelIdArray || !m_ugrid)
        {
            return;
        }

        QHash<int, bool> modelVisibilityHash;

        // Save the visibility first, and check if the visbility has changed at the same time.
        bool hasChanged = false;
        for (int modelId : m_modelIds)
        {
            bool parentVisible = getParentDataVisibility(modelId);
            bool oldVisibility = false;
            if (m_modelVisibilityHash.contains(modelId))
            {
                oldVisibility = m_modelVisibilityHash[modelId];
            }

            if (parentVisible != oldVisibility)
            {
                hasChanged = true;
            }

            if (!modelVisibilityHash.contains(modelId))
            {
                modelVisibilityHash.insert(modelId, parentVisible);
            }
        }

        // If the visibility hasn't changed, then return.
        if (!hasChanged && !m_modelVisibilityHash.isEmpty())
        {
            return;
        }

        // Reset the extractor's ids.
        resetVtkObj(m_idArray);

        // Update the extractor's ids.
        for (int i = 0; i < m_ugrid->GetNumberOfPoints(); i++)
        {
            int modelId = m_modelIdArray->GetValue(i);
            bool visibility = modelVisibilityHash[modelId];
            if (visibility)
            {
                m_idArray->InsertNextValue(i);
            }
        }

        // Update the data.
        m_ugrid->Modified();

        // Save the flags.
        m_modelVisibilityHash = modelVisibilityHash;
    }

    bool FITKGraphObjectPredefinedFieldBase::generatePolyData(Interface::FITKAbaAbstractPredefinedField* fieldData)
    {
        // Get the component id.
        int compId = fieldData->getComponentID();

        // Try to get set and surface poly data.
        QList<int> modelIds;
        QList<int> subSetIds;

        // Generate locations grid.
        vtkSmartPointer<vtkUnstructuredGrid> locGrid = vtkSmartPointer<vtkUnstructuredGrid>::New();
        generateComponentGridData(locGrid, fieldData, modelIds, subSetIds, false);

        // Save the model ids.
        m_modelIds << modelIds;

        // Save the sub set ids.
        m_subSetIds << subSetIds;

        // Save the component id.
        m_locationCompId = compId;

        // Save if the components are surface.
        int instanceId = -1;
        Interface::FITKAbstractAssInstance* instance = fieldData->getTModelObj<Interface::FITKAbstractAssInstance>();
        if (instance)
        {
            instanceId = instance->getDataObjectID();
        }

        // Add points for location component.
        //@{
        // Get the model id array of location.
        vtkIntArray* modelIdArray = vtkIntArray::SafeDownCast(locGrid->GetPointData()->GetArray(FITKSetSurfPolyProvider::getInstance()->getModelIdsArrayName()));

        for (int i = 0; i < locGrid->GetNumberOfPoints(); i++)
        {
            int modelId = -1;
            if (modelIdArray)
            {
                modelId = modelIdArray->GetValue(i);
            }

            // Add a point and a cell.
            //@{
            // Add point.
            int ptId = m_pointsLoc->InsertNextPoint(locGrid->GetPoint(i));

            // Add cell.
            vtkSmartPointer<vtkIdList> cell = vtkSmartPointer<vtkIdList>::New();
            cell->InsertNextId(ptId);
            m_gridLoc->InsertNextCell(VTKCellType::VTK_VERTEX, cell);

            // Add model id and normal.
            m_modelIdArrayLoc->InsertNextValue(modelId);
            //@}
        }
        //@}

        return true;
    }

    bool FITKGraphObjectPredefinedFieldBase::generateLocPolyData(Interface::FITKBoundaryLocation* loc, QList<int>& modelIds, vtkUnstructuredGrid* ugrid)
    {
        // Get the location id.
        if (!loc || !ugrid)
        {
            return false;
        }

        // Get the surface id.
        int compId = loc->getComponentID();

        // Try to get the instance id.
        int instanceIdLoc = -1;

        Interface::FITKAbstractAssInstance* instance = loc->getTModelObj<Interface::FITKAbstractAssInstance>();
        if (instance)
        {
            instanceIdLoc = instance->getDataObjectID();
        }

        // Try to get set and surface poly data for location.
        Interface::FITKModelSet* set = Core::FITKDataRepo::getInstance()->getTDataByID<Interface::FITKModelSet>(compId);
        if (set)
        {
            generateSetData(ugrid, set, modelIds, instanceIdLoc);
        }
        else
        {
            vtkUnstructuredGrid* surfGridLoc = FITKSetSurfPolyProvider::getInstance()->getSurfaceGridData(compId, modelIds, instanceIdLoc);
            if (!surfGridLoc)
            {
                return false;
            }

            ugrid->DeepCopy(surfGridLoc);
        }

        // Remove the normals data.
        FITKGraphCommons::RemoveCellNormals(ugrid);

        return true;
    }

    void FITKGraphObjectPredefinedFieldBase::generateGraph()
    {
        // Override in sub-class.
    }

    bool FITKGraphObjectPredefinedFieldBase::getDataVisibility()
    {
        bool isActive = false;

        // Try to down cast to interaction data.       
        Interface::FITKAbaAbstractPredefinedField* field = dynamic_cast<Interface::FITKAbaAbstractPredefinedField*>(_dataObj);
        if (field)
        {
            isActive = field->getActiveState();
        }  
        
        return isActive;
    }

    void FITKGraphObjectPredefinedFieldBase::generateComponentGridData(vtkUnstructuredGrid* setSurfGrid, Interface::FITKBoundaryLocation* loc
        , QList<int>& modelIds, QList<int>& subSetIds, bool removeNormals)
    {
        if (!setSurfGrid || !loc)
        {
            return;
        }

        // Get the location component id.
        int compId = loc->getComponentID();

        // Try to get the instance id.
        int instanceId = -1;

        Interface::FITKAbstractAssInstance* instance = loc->getTModelObj<Interface::FITKAbstractAssInstance>();
        if (instance)
        {
            instanceId = instance->getDataObjectID();
        }

        this->Superclass::generateComponentGridData(compId, setSurfGrid, modelIds, subSetIds, instanceId, removeNormals);
    }

    void FITKGraphObjectPredefinedFieldBase::setFITKRender(Comp::FITKGraphRender* render)
    {
        if (!render)
        {
            return;
        }

        vtkRenderer* renderer = render->getRenderer();
        if (!renderer)
        {
            return;
        }

        // Set the renderer for calculating size.
        for (vtkDistanceToCamera* obj : m_distToCameraList)
        {
            if (obj)
            {
                obj->SetRenderer(renderer);
            }
        }

        // Initialize the normal.
        updateCameraNormal();

        // Save the render.
        this->Superclass::setFITKRender(render);
    }

    void FITKGraphObjectPredefinedFieldBase::simplified(bool enabled)
    {
        if (!m_graphSimplified)
        {
            return;
        }

        // Change the actor visibility while changing the camera.
        if (m_fActor && getDataVisibility())
        {
            m_fActor->SetVisibility(!enabled);
        }
    }

    bool FITKGraphObjectPredefinedFieldBase::isRelatedTo(int dataObjId)
    {
        // Check if the id is the self-object id.
        bool isTheObj = FITKGraphObject3D::isRelatedTo(dataObjId);
        if (isTheObj)
        {
            return true;
        }

        // Check if the model ids contains the data id.
        bool hasModelRelated = m_modelIds.contains(dataObjId);
        if (hasModelRelated)
        {
            return true;
        }

        // Check if the location is the same as the data.
        bool hasLocRelated = (m_locationCompId == dataObjId);
        if (hasLocRelated)
        {
            return true;
        }

        // Check if the data id is one of the sub set id.
        bool isSubSet = m_subSetIds.contains(dataObjId);
        if (isSubSet)
        {
            return true;
        }
        
        return false;
    }

    bool FITKGraphObjectPredefinedFieldBase::getFixedBounds(double* bounds)
    {
        // If the graph object is invisible, then return.
        bool visibile = getDataVisibility();
        if (!visibile)
        {
            return false;
        }

        // Get the components' bounds only.
        if (!m_extractSelection)
        {
            return false;
        }

        double bds[6] = { 0., 0., 0., 0., 0., 0. };
        bool flag = FITKGraphCommons::GetDataSetBounds(m_extractSelection->GetOutput(), bds);
        if (!flag)
        {
            return false;
        }

        for (int i = 0; i < 6; i++)
        {
            bounds[i] = bds[i];
        }

        return true;
    }
}   // namespace Exchange